/*

  The MIT License

  Copyright (c) 2008 - 2010. Dimarik

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.

*/

package ru.goodcore.display
{

import flash.geom.Rectangle;
import flash.utils.ByteArray;
import flash.utils.Endian;

import ru.goodcore.utils.traceByteArray;


/**
 * Класс преобразует GIF изображение в SWF bytearray. Этот SWF содержит один 
 * экспортируемый класс, наследуемый от MovieClip. Его полное название
 * определяется параметром <code>qname</code>. Кадры этого класса содержат 
 * заливки (shape fills) кадров исходного GIF изображения. Если задан параметр 
 * <code>frameRate</code> больше нуля, то кадры будут распределены с учетом 
 * таймингов исходного GIF изображения.
 * 
 * <b>Внимание!</b> Не тестировано с dispose = 3.
 * 
 * <b>Known issue</b>
 * http://livegif.ru/Gallery/BEST/CAT1.GIF - Red background on one of frames
 * http://livegif.ru/Gallery/POSTCARD/love_friend/skuka/pic5413.gif - border disappear
 * 
 * The class converts GIF image to SWF bytearray.
 * 
 * 
 * @author			Dimarik
 * @version			1.2
 * @langversion		3.0
 * @playerversion	9.0
 * 
 * @see http://www.w3.org/Graphics/GIF/spec-gif89a.txt
 */
public class SWFGIFMovieClip extends ByteArray {

	//-------------------------------------------------------------------------
	//
	//   Embed resources
	//
	//-------------------------------------------------------------------------

	[Embed (source='../../../../src-assets/images/no-image_icon.gif', mimeType='application/octet-stream')]
	//[Embed (source='../../../../src-assets/images/brokenImage.gif', mimeType='application/octet-stream')]
	private static const noImageByteArrayClass:		Class;

	//-------------------------------------------------------------------------
	//
	//   Class methods
	//
	//-------------------------------------------------------------------------

	/**
	 *  Приводит число к виду 000i, где len - результирующее кол-во знаков
	 *
	 *  @param	i		число для преобразования
	 *  @param	len		результирующее количество знаков
	 *  @param	radix	radix и есть
	 */
	public static function formatNumber(i:uint, len:uint = 3, radix:uint = 10, fill:String = '0'):String {
		var s:String = i.toString(radix);
		while (s.length < len) 
			s = fill + s;
		return s;
	}

	//-------------------------------------------------------------------------
	//
	//   Constructor
	//
	//-------------------------------------------------------------------------

	/**
	 * Constructor
	 * 
	 * @param input		GIF ByteArray stream.
	 * @param qname		Full qualified class name.
	 * @param smoothing	Use bitmap smoothing. 
	 * @param frameRate	If provided and <code>frameRate</code> is greater than zero, 
	 * 					GIF frames will be stretched along MovieClip timeline according 
 	 * 					GIF frame delay information.
	 */
	public function SWFGIFMovieClip(input:ByteArray, qname:QName, 
							smoothing:Boolean = false, frameRate:uint = 0)
	{
		super();
		this.process(input, qname, smoothing, frameRate);
	}

	//-------------------------------------------------------------------------
	//
	//   Private variables
	//
	//-------------------------------------------------------------------------

	/**
	 * @private 
	 */
	private var _fillStyleStartPosition:	uint = 0;

	/**
	 * @private 
	 */
	private var _fillStyleLength:			uint = 0;

	/**
	 * @private 
	 * Fill styles counter
	 */
	private var _fillStylesCount:			uint = 1;

	/**
	 * @private 
	 * Last shaperecord bit position
	 */
	private var _shapeRecordBitPosition:	uint = 0;

	/**
	 * @private 
	 */
	private var _shapeRecordStartPosition:	uint = 0;

	/**
	 * @private 
	 */
	private var _shapeRecordLength:			uint = 0;

	/**
	 * @private
	 */
	private var _delays:					Array = new Array();

	//-------------------------------------------------------------------------
	//
	//   Private methods
	//
	//-------------------------------------------------------------------------
	/**
	 * @private
	 */
	private function process(input:ByteArray, qname:QName, 
									smoothing:Boolean, frameRate:uint):void
	{
		super.endian = 
		input.endian = Endian.LITTLE_ENDIAN;

		var useDelays:	Boolean = frameRate > 0;

		// Magic bytes
		var data:Array = [
			0x09535746, 0x00000000, 0xA0000A30, 0x00010C00, 
			0x00081144, 0x02430000, 0xBFFFFFFF, 0x00000B15,
			0x53000100, 0x656E6563, 0x00003120,

			0x10000000, 0x00002E00, 0x616C660D,
			0x642E6873, 0x6C707369, 0x4D097961, 0x6569766F,
			0x70696C43, 0x624F0600, 0x7463656A, 0x616C660C,
			0x652E6873, 0x746E6576, 0x76450F73, 0x44746E65,
			0x61707369, 0x65686374, 0x69440d72, 0x616C7073,
			0x6A624F79, 0x11746365, 0x65746E49, 0x74636172,
			0x4F657669, 0x63656A62, 0x69441674, 0x616C7073,
			0x6A624F79, 0x43746365, 0x61746E6F, 0x72656E69,
			0x72705306, 0x06657469, 0x03160116, 0x06160518,
			0x09000816, 0x07020107, 0x04070402, 0x09050707,
			0x070A0207, 0x02070B02, 0x0D02070C, 0x00000003,
			0x00000000, 0x00000000, 0x01010000, 0x00030802,
			0x00000001, 0x01010201, 0x03000104, 0x09010100,
			0x30D0030A, 0x01000047, 0x0B0A0101, 0xD030D006,
			0x00470049, 0x01020200, 0xD0270901, 0x60006530,
			0x04603003, 0x30056030, 0x60300660, 0x08603007,
			0x30026030, 0x00580260, 0x1D1D1D1D, 0x681D1D1D,
			0x00004701,
			
			0x01060686, 0x00000200
		];

		var i:					uint = 0;
		var byte:				uint;
		var tagStartPosition:	uint;

		for (; i < 11; super.writeUnsignedInt(data[i++])) void;

		tagStartPosition = super.position;

		super.writeShort(0x0001);		
		for (; i < 13; super.writeUnsignedInt(data[i++])) void;
		super.writeShort(0x0000);

		if (qname.uri) {
			super.writeByte(0x0E);
			super.writeByte(qname.uri.length);
			super.writeUTFBytes(qname.uri);
		} else {
			super.writeShort(0x0C00);
		}

		super.writeByte(qname.localName.length);
		super.writeUTFBytes(qname.localName);

		for (; i < 19; super.writeUnsignedInt(data[i++])) void;

		byte = qname.localName.length + 1;

		if (qname.uri)
			byte += qname.uri.length;

		super.writeByte(byte);

		if (qname.uri)
			super.writeUTFBytes(qname.uri + ':');

		super.writeUTFBytes(qname.localName);

		for (; i < 79; super.writeUnsignedInt(data[i++])) void;

		this.writeRecordHeader(0x52, this, tagStartPosition);
		
		//-----------------------------
		//  Assemble gif frames
		//-----------------------------

		var charID:		uint = 1;
		var imgCount:	uint = 0;
		var gifHeader:	ByteArray = new ByteArray();
		var gifFrame:	ByteArray = new ByteArray();

		gifHeader.endian = 
		gifFrame.endian = Endian.LITTLE_ENDIAN;

		//-----------------------------
		// Header String[6] 
		// for example GIF87a, GIF89a 
		//-----------------------------
		// TODO Should we throw an error if bad signature?
		/** signature & version [ignore] */
		//input.position = 6;

		var signature:String;

		if (
			   input.bytesAvailable > 6 
			&& (signature = input.readUTFBytes(6))
			&& signature != 'GIF87a'
			&& signature != 'GIF89a'
		) {
			input = new noImageByteArrayClass() as ByteArray;
			input.endian = Endian.LITTLE_ENDIAN;
			input.position = 6;
		}

		//-----------------------------
		// Logical Screen Descriptor
		//-----------------------------

		/** canvas width & height in pixels UI16 both */
		var imgWidth:	uint = input.readUnsignedShort();
		var imgHeight:	uint = input.readUnsignedShort();

		/** <Packed Fields> UI8 */
		byte = input.readUnsignedByte();

		/** bit 7    global color table flag           */
		/** bit 4-6  color resolution         [ignore] */
		/** bit 3    gct sort flag            [ignore] */
		/** bit 0-2  gct size                          */
		var gctSize:	uint = 2 << (byte & 0x07);

		/** background color index UI8 [ignore] */
		/** pixel aspect ratio UI8 [ignore]     */
		input.position += 2; 

		if ( (byte & 0x80) > 0 ) { // has global color table
			input.position += gctSize * 3; // skip global color table
		}

		gifHeader.writeBytes(input, 0, input.position);

		//-----------------------------
		// GIF file content blocks
		//-----------------------------

		var run:			Boolean = true;
		var block:			ByteArray;

		while ( run ) {
			/** Extension Introducer */
			byte = input.readUnsignedByte();

			switch (byte) {

				/** Beginning of an extension block */
				case 0x21: {

					/** Extention Label */
					byte = input.readUnsignedByte();

					switch (byte) {

						/** Graphic Control Extension */
						case 0xF9: {

							gifFrame.writeBytes(gifHeader);
							gifFrame.writeByte(0x21); // Extension Introducer
							gifFrame.writeByte(0xF9); // Graphic Control Extension

							// block size except Terminator byte!
							var size:uint = input.readUnsignedByte();
							gifFrame.writeByte(size);

							byte = input.readUnsignedByte();
							gifFrame.writeByte(byte);
							/** <Packed Fields> UI8*/
							/** bit 5-7 Reserved               [ignore] */
							/** bit 2-4 Disposal Method                 */
							/**
							 * 0 - No disposal specified. Do nothing.
							 * 1 - Do not dispose. The graphic is to be left in place.
							 * 2 - Restore to background color.
							 * 3 - Restore to previous.
							 */
							var dispose:uint = (byte >> 2 & 0x03) || 1;
							/** bit 1   User Input Flag        [ignore] */
							/** bit 0   Transparent Color Flag [ignore] */

							/** Delay Time before continuing (in 1/100 seconds) UI16 */
							var delay:uint = input.readUnsignedShort();
							gifFrame.writeShort(delay);

							if (useDelays)
								this._delays.push(delay);

							/** Transparency Index UI8 */
							var transparencyIndex:uint = input.readUnsignedByte();
							gifFrame.writeByte(transparencyIndex);

							/** Block Terminator UI8 */
							byte = input.readUnsignedByte();
							gifFrame.writeByte(byte);
							break;
						}


						/** [ignore] */
						default: {
							ignoreGIFBlock(input);
							break;
						}
					}
					break;
				}


				/** Image Separator */
				case 0x2C: {

					/** Image Left Position */
					var left:	uint = input.readUnsignedShort();
					/** Image Top Position  */
					var top:	uint = input.readUnsignedShort();
					/** Image Width */
					var width:	uint = input.readUnsignedShort();
					/** Image Image Height  */
					var height:	uint = input.readUnsignedShort();

					if (gifFrame.length == 0) {
						gifFrame.writeBytes(gifHeader);
					}

					// Writes corrected width & height
					gifFrame.position = 6;
					gifFrame.writeShort(width);
					gifFrame.writeShort(height);

					// Return to end
					gifFrame.position = gifFrame.length;

					gifFrame.writeByte(0x2C); // Image Separator itself
					gifFrame.writeShort(0x00); // left
					gifFrame.writeShort(0x00); // top
					gifFrame.writeShort(width); // width
					gifFrame.writeShort(height); // height

					var shapeRect:	Rectangle = new Rectangle(0, 0, imgWidth, imgHeight);
					var imgRect:	Rectangle = new Rectangle(left, top, width, height);

					/** <Packed Fields>     */
					byte = input.readUnsignedByte();
					gifFrame.writeByte(byte);

					/** bit 7   Local Color Table Flag             */
					var hasLCT:	Boolean = (byte & 0x80) > 0;
					/** bit 6   Interlace Flag           */
					/** bit 5   Sort Flag                 [ignore] */
					/** bit 3,4 Reserved                  [ignore] */
					/** bit 0-2 Size of Local Color Table          */
					var lctSize:	uint = 2 << (byte & 0x07);

					if (hasLCT) {
						gifFrame.writeBytes(input, input.position, lctSize * 3);
						input.position += lctSize * 3;
					}

					/** LZW minimum symbol size */
					byte = input.readUnsignedByte();
					gifFrame.writeByte(byte);

					var tmpBA:	ByteArray;

					do {
						tmpBA = readGIFBlock(input);
						gifFrame.writeBytes(input, input.position - (tmpBA.length + 1), tmpBA.length + 1);
					} while (tmpBA.length > 0)

					// Write Terminator
					gifFrame.writeByte(0x3B);

					imgCount++;

					this.writeDefineBitsJPEG2( gifFrame, charID, this );

					var reuse:Boolean = dispose == 1;
					this.writeDefineShape(shapeRect, imgRect, charID + 1, charID, smoothing, reuse);

					charID += 2;
					gifFrame.length = 0;
					break;
				}

				/** Terminator. Astalavista, baby! */
				case 0x3B: {
					run = false;
					break;
				}
			}
		}

		// DefineSprite
		tagStartPosition = super.position;
		super.writeShort(imgCount * 2 + 1);

		// Will be updated later
		// FrameCount UI16 
		super.position = super.position + 2;

		for (; i < 81; super.writeUnsignedInt(data[i++])) void;

		if (useDelays) {
			var frameTime:Number = 1 / frameRate * 100;
			var delays:			Array = this._delays;
			var frameCounter:	uint = 0;
			var numFrames:		uint = Math.floor(delays[frameCounter++] / frameTime) | 1;
			var totalFrames:	uint = 0;
			// SHOWFRAME
			while (numFrames--) {
				totalFrames++;
				super.writeShort(0x0040);
			}
		} else {
			// SHOWFRAME
			super.writeShort(0x0040);
		}

		charID = 4;

		if (useDelays) {

			while (charID <= imgCount * 2) {
				super.writeUnsignedInt(0x01030685);
				super.writeByte(0x00);
				super.writeShort(charID);
				numFrames = Math.floor(delays[frameCounter++] / frameTime) | 1;

				while (numFrames--) {
					totalFrames++;
					super.writeShort(0x0040);
				}

				charID += 2;
			}

			// Update FrameCount
			super.position = tagStartPosition + 2;
			super.writeShort(totalFrames);
			super.position = super.length;

		} else {

			while (charID <= imgCount * 2) {
				super.writeUnsignedInt(0x01030685);
				super.writeByte(0x00);
				super.writeShort(charID);
				super.writeShort(0x0040);
				charID += 2;
			}

		}

		// END
		super.writeShort(0x0000);
		this.writeRecordHeader(0x27, this, tagStartPosition);

		// SymbolClass
		tagStartPosition = super.position;
		super.writeShort(0x0001);
		super.writeShort(imgCount * 2 + 1);

		if (qname.uri)
			super.writeUTFBytes(qname.uri + '.');

		super.writeUTFBytes(qname.localName);
		super.writeByte(0x00);

		this.writeRecordHeader(0x4C, this, tagStartPosition);

		super.writeUnsignedInt(0x000040);
		super.position = 4;
		super.writeUnsignedInt(super.length);
		super.position = 0;

		//getDefinitionNames(this);
	}

	//-------------------------------------------------------------------------
	//
	//   Private methods
	//
	//-------------------------------------------------------------------------

	/**
	 * @private
	 * @param ba
	 * @param bmpd
	 * 
	 */
	private function writeDefineBitsJPEG2(input:ByteArray, charID:uint, ba:ByteArray):void {
		var pos:uint = ba.position;
		ba.writeShort(charID);
		ba.writeBytes(input);
		this.writeRecordHeader(0x15, ba, pos);
	}

	/**
	 * @private
	 * @param ba
	 * @param bmpd
	 * 
	 */
	private function writeDefineShape(shapeRect:Rectangle, rect:Rectangle, charID:uint, 
					BitmapId:uint, smoothing:Boolean, reuse:Boolean = false):void
	{
		var tagStartPosition:	uint = super.position;

		// ShapeId UI16
		super.writeShort(charID);

		// ShapeBounds RECT
		this.writeRect(shapeRect, this);

		// SHAPEWITHSTYLE
		var pos:	uint = super.position;

		// copy last fillstyles
		if (this._fillStyleStartPosition > 0)
			super.writeBytes(this, this._fillStyleStartPosition, this._fillStyleLength);

		this._fillStyleStartPosition = pos;

		// Correct fillstyles array length
		super.position = pos;
		super.writeByte(this._fillStylesCount);
		super.position = super.length;

		var buffer:			String = '';

		//		- FillStyles
		//			- FillStyleType UI8
		if (smoothing) {
			buffer += formatNumber(0x41, 8, 2); // Fill with 0x41 = clipped bitmap fill
		} else {
			buffer += formatNumber(0x43, 8, 2); // Fill with 0x43 = non-smoothed clipped bitmap
		}
		//			- BitmapId UI16
		var tmp:String = formatNumber(BitmapId, 16, 2);
		buffer += tmp.substr(8);
		buffer += tmp.substr(0, 8);

		while (buffer.length) {
			super.writeByte( parseInt(buffer.substr(0, 8), 2) );
			buffer = buffer.substr(8);
		}

		// Assemble MATRIX
		//			- BitmapMatrix MATRIX
		var bitsValue:		String;
		var numBitsCount:	uint;
		var numBits:		String;

		var TranslateX:		uint = rect.x * 20;
		var TranslateY:		uint = rect.y * 20;
		var maxValue:		uint = Math.max(TranslateY, TranslateX);

		if (maxValue) {
			numBitsCount = maxValue.toString(2).length + 1;
			numBits = formatNumber(numBitsCount, 5, 2);
			
			super.writeUnsignedInt(0x050040D9);
			super.writeShort(0x0000);

			buffer = '000' + numBits;

			bitsValue = formatNumber(TranslateX, numBitsCount, 2);
			buffer += bitsValue;

			bitsValue = formatNumber(TranslateY, numBitsCount, 2);
			buffer += bitsValue;

			while (buffer.length % 8)
				buffer += '0';

			while (buffer.length) {
				super.writeByte( parseInt(buffer.substr(0, 8), 2) );
				buffer = buffer.substr(8);
			}

		}

		else {
			super.writeUnsignedInt(0x050040D9);
			super.writeShort(0x0000);
			super.writeByte(0x00);
		}

		this._fillStyleLength = super.position - this._fillStyleStartPosition;
		

		pos = super.position;

		// NumFillBits & NumLineBits
		var NumFillBits:uint = 0x0F; // set to maximum

		// copy last shaperecord
		if (this._shapeRecordStartPosition > 0) {
			super.writeBytes(this, this._shapeRecordStartPosition, this._shapeRecordLength);
			//trace(traceByteArray(this, super.position - this._shapeRecordLength));
		} else {
			//  - LINESTYLEARRAY
			//		- LineStyleCount UI8
			super.writeByte(0x00);
			super.writeByte( NumFillBits << 4 | 0 );
		}

		if (this._shapeRecordBitPosition > 0) {
			// last shape record byte exept ENDSHAPERECORD
			buffer = formatNumber(this[super.length - 1], 8, 2).substr(0, this._shapeRecordBitPosition);
			super.position = super.position - 1;
		} else {
			buffer = '';
		}

		this._shapeRecordStartPosition = pos;

		// ShapeRecords
		//		- STYLECHANGERECORD
		var fillIndex:String = formatNumber(this._fillStylesCount, NumFillBits, 2);

		if (TranslateX || TranslateY) {
			//		- STYLECHANGERECORD [MoveBits] & [StateFillStyle1]
			buffer += '000101';

			maxValue = Math.max(TranslateY, TranslateX);

			numBitsCount = maxValue.toString(2).length + 1;
			numBits = formatNumber(numBitsCount, 5, 2);

			buffer += numBits;

			bitsValue = formatNumber(TranslateX, numBitsCount, 2);
			buffer += bitsValue;

			bitsValue = formatNumber(TranslateY, numBitsCount, 2);
			buffer += bitsValue;

			buffer += fillIndex;
		} else {
			// 		- STYLECHANGERECORD [StateFillStyle1] only
			buffer += '000100' + fillIndex;
		}

		var width:			uint = rect.width * 20;
		var height:			uint = rect.height * 20;

		maxValue = Math.max(width, height);

		numBitsCount = maxValue.toString(2).length + 1;
		numBits	= formatNumber(numBitsCount - 2, 4, 2);

		//		- STRAIGHTEDGERECORD Move to right
		bitsValue = formatNumber(width , numBitsCount, 2);
		tmp = '11' + numBits + '00' + bitsValue;
		buffer += tmp;

		//		- STRAIGHTEDGERECORD Move to down
		bitsValue = formatNumber(height, numBitsCount, 2);
		tmp = '11' + numBits + '01' + bitsValue;
		buffer += tmp;

		//		- STRAIGHTEDGERECORD Move to left
		var bitString:String = formatNumber(width, numBitsCount, 2);
		var aaa:String = bitString.replace(/0/g, '2');
		aaa = aaa.replace(/1/g, '0');
		aaa = aaa.replace(/2/g, '1');
		var ddd:uint = parseInt(aaa, 2) + 1;
		bitsValue = formatNumber(ddd, numBitsCount, 2, '1');
		tmp = '11' + numBits + '00' + bitsValue;
		buffer += tmp;

		//		- STRAIGHTEDGERECORD Move to up
		bitString = formatNumber(height, numBitsCount, 2);
		aaa = bitString.replace(/0/g, '2');
		aaa = aaa.replace(/1/g, '0');
		aaa = aaa.replace(/2/g, '1');
		ddd = parseInt(aaa, 2) + 1;
		bitsValue = formatNumber(ddd, numBitsCount, 2, '1');
		tmp = '11' + numBits + '01' + bitsValue;
		buffer += tmp;

		this._shapeRecordBitPosition = buffer.length % 8;

		// Сколько бит уже записано как ENDSHAPERECORD
		var endBitsCountHas:uint = 0;
		while (buffer.length % 8) {
			buffer += '0';
			endBitsCountHas++;
		}

		while (buffer.length) {
			super.writeByte( parseInt(buffer.substr(0, 8), 2) );
			buffer = buffer.substr(8);
		}

		this._shapeRecordLength = super.position - this._shapeRecordStartPosition;

		var endBitsCountRemain:uint = 6 - endBitsCountHas;

		if (endBitsCountRemain > 0) {
			// Записать оставшиеся endBitsCountRemain ENDSHAPERECORD + padding
			super.writeByte(0x00);
		}

		// Tag type = 2
		var headerLen:uint = this.writeRecordHeader(0x02, this, tagStartPosition);

		if (reuse) {
			this._fillStyleStartPosition += headerLen;
			this._shapeRecordStartPosition += headerLen;
			this._fillStylesCount++;
		} else {
			// reset
			this._fillStyleStartPosition = 0;
			this._fillStylesCount = 1;
			this._shapeRecordStartPosition = 0;
			this._shapeRecordBitPosition = 0;
		}
	}


	/**
	 * @private 
	 * @param rectangle
	 * @param ba
	 */
	private function writeRect(rectangle:Rectangle, input:ByteArray):void {
		var xMin:	Number = rectangle.x * 20;
		var xMax:	Number = xMin + rectangle.width * 20;
		var yMin:	Number = rectangle.y * 20;
		var yMax:	Number = yMin + rectangle.height * 20;

		var max:	Number = Math.max(xMax, yMax);
		var uuint:	uint = max.toString(2).length + 1;

		var target:String = formatNumber(uuint, 5, 2);
		target += formatNumber(xMin, uuint, 2);
		target += formatNumber(xMax, uuint, 2);
		target += formatNumber(yMin, uuint, 2);
		target += formatNumber(yMax, uuint, 2);

		if (target.length % 8)
			uuint = 8 - target.length % 8;

		while (uuint--)
			target += '0';

		while (target.length) {
			uuint = parseInt(target.substr(0, 8), 2);
			target = target.substr(8);
			input.writeByte(uuint);
		}
	}

	/**
	 * @private
	 * @param id
	 * @param ba
	 * @param position
	 * @return RECORDHEADER length
	 * 
	 */
	private function writeRecordHeader(id:uint, input:ByteArray, position:uint = 0):uint {
		var length:		uint = input.length - position;

		if (length > 0x3E) {
			var headerLen:	uint = 6;
			input.position = position + 6;
			input.writeBytes(input, position);
			input.position = position;
			input.writeShort(id << 6 | 0x3F);
			input.writeUnsignedInt(length);
		} else {
			headerLen = 2;
			input.position = position + 2;
			input.writeBytes(input, position);
			input.position = position;
			input.writeShort(id << 6 | length);
		}

		input.position = input.length;

		return headerLen;
	}

	/**
	 * @private
	 * Reads next variable length block from input.
	 *
	 * @return ByteArray contains block bytes
	 */
	private function readGIFBlock(input:ByteArray):ByteArray {
		var blockSize:	uint = input.readUnsignedByte();
		var result:		ByteArray = new ByteArray();

		if (blockSize > 0)
			input.readBytes(result, 0, blockSize);

		return result;
	}

	/**
	 * @private
	 */
	private function ignoreGIFBlock(ba:ByteArray):void {
		var subblock:		ByteArray;
		while ( (subblock = readGIFBlock(ba)).length > 0 ) void;
	}


}
}

